﻿//------------------------------------------------------------------------------
//  此代码版权声明为全文件覆盖，如有原作者特别声明，会在下方手动补充
//  此代码版权（除特别声明外的代码）归作者本人Diego所有
//  源代码使用协议遵循本仓库的开源协议及附加协议
//  Gitee源代码仓库：https://gitee.com/diego2098/ThingsGateway
//  Github源代码仓库：https://github.com/kimdiego2098/ThingsGateway
//  使用文档：https://thingsgateway.cn/
//  QQ群：605534569
//------------------------------------------------------------------------------

namespace ThingsGateway.NewLife.X.Algorithms;

/// <summary>
/// 拉格朗日插值
/// </summary>
public class LagrangeInterpolation
{
    /// <summary>
    /// 初始化拉格朗日插值
    /// </summary>
    /// <param name="times">X各点坐标组成的数组</param>
    /// <param name="values">X各点对应的Y坐标值组成的数组</param>
    public LagrangeInterpolation(Int32[] times, Double[] values)
    {
        Times = times;
        Values = values;
        Threshold = times.Length;
    }

    /// <summary>
    /// x数组或者y数组中元素的个数, 注意两个数组中的元素个数需要一样
    /// </summary>
    public Int32 Threshold { get; set; }

    /// <summary>
    /// X各点坐标组成的数组
    /// </summary>
    public Int32[] Times { get; set; }

    /// <summary>
    /// X各点对应的Y坐标值组成的数组
    /// </summary>
    public Double[] Values { get; set; }

    /// <summary>
    /// 获得某个横坐标对应的Y坐标值
    /// </summary>
    /// <param name="time">x坐标值</param>
    /// <returns></returns>
    public Double GetValue(Int32 time)
    {
        //返回值
        var value = 0.0;
        //如果初始的离散点为空, 返回0
        if (Threshold < 1) return value;

        //如果初始的离散点只有1个, 返回该点对应的Y值
        if (Threshold == 1)
        {
            value = Values[0];
            return value;
        }
        //如果初始的离散点只有2个, 进行线性插值并返回插值
        if (Threshold == 2)
        {
            value = (Values[0] * (time - Times[1]) - Values[1] * (time - Times[0])) / (Times[0] - Times[1]);
            return value;
        }
        //用于累乘数组始末下标
        Int32 start, end;
        //如果插值点小于第一个点X坐标, 取数组前3个点做插值
        if (time <= Times[1])
        {
            start = 0;
            end = 2;
        }
        //如果插值点大于等于最后一个点X坐标, 取数组最后3个点做插值
        else if (time >= Times[Threshold - 2])
        {
            start = Threshold - 3;
            end = Threshold - 1;
        }
        //除了上述的一些特殊情况, 通常情况如下
        else
        {
            start = 1;
            end = Threshold;
            Int32 temp;
            //使用二分法决定选择哪三个点做插值
            while ((end - start) != 1)
            {
                temp = (start + end) / 2;
                if (time < Times[temp - 1])
                    end = temp;
                else
                    start = temp;
            }
            start--;
            end--;
            //看插值点跟哪个点比较靠近
            if (Math.Abs(time - Times[start]) < Math.Abs(time - Times[end]))
                start--;
            else
                end++;
        }
        //这时已经确定了取哪三个点做插值, 第一个点为x[start]
        Double valueTemp;
        //注意是二次的插值公式
        for (var i = start; i <= end; i++)
        {
            valueTemp = 1.0;
            for (var j = start; j <= end; j++)
                if (j != i)
                    valueTemp *= (time - Times[j]) / (Double)(Times[i] - Times[j]);
            value += valueTemp * Values[i];
        }
        return value;
    }
}
